Работа с GLSL шейдерами.
Инициализация шейдеров в OpenGL-программе.

Перед тем как непосредственно перейти к теме статьи, кратко напомню, что такое шейдеры. Шейдеры, это программы, которые в отличии от обычных программ, выполняются на графическом процессоре видеокарты. Т.е. шейдеры предназначены для замены фиксированной последовательности выполнения некоторых команд по отрисовке графических примитивов. Это придаёт графическим приложениям определённую гибкость и облегчает для программиста реализацию различных видеоэффектов. Первоначально шейдеры писались на ассемблероподобном языке низкого уровня, что было не совсем удобно для программистов. Поэтому советом по пересмотру архитектуры OpenGL (OpenGL Arсhitecture Review Board или сокращённо ARB), куда входят такие известные фирмы как 3Dlabs, Apple, ATI, Dell, Evans & Sutherland, Hewlett-Packard, IBM, Intel, Matrox, NVIDIA, SGI и Sun Microsystems был разработан высокоуровневый язык для написания шейдеров, который назвали GLSL (OpenGL Shading Language). Официально GLSL присутствует в OpenGL версии 2.0, а неофициально, в виде ARB-расширения в OpenGL версии 1.5. GLSL основан на синтаксисе языка С. Конечно имеются некоторые отличия между синтаксисом GLSL и С, но они незначительны и указаны в спецификации по GLSL.

Рассмотрим, как в вашей OpenGL программе инициализировать шейдеры. Для инициализации GLSL-шейдеров необходимо выполнить следующую последовательность действий:

1. Проверить поддержку драйвером OpenGL расширений GL_ARB_shader_objects и GL_ARB_shading_language_100 (поддержка GLSL верисии 1.0).

2. Проверить поддержку драйвером OpenGL функций-расширений, необходимых для работы с GLSL-шейдерами.

3. Создать пустые объекты-шейдеры с помощью glCreateShaderObjectARB(). Их может быть один, а может быть и более. Всё зависит от потребностей вашей программы.

4. Считать исходный текст шейдеров из файла (если он содержится во внешнем файле) и при помощи функции glShaderSourceARB() поместить в них исходный код.

5. Выполнить компиляцию каждого шейдера с помощью glCompileShaderARB().

6. При помощи функции glCreateProgramObjectARB() создать объект-программу.

7. Присоединить все объекты-шейдеры к объекту-программе с помощью glAttachObjectARB().

8. Скомпоновать объект-программу командой glLinkProgramARB().

9. Командой glUseProgramObjectARB() установить исполняемую программу в качестве текущей для OpenGL.

А сейчас более подробно рассмотрим приведённую выше последовательность действий на примере функций, которые реализованы в классе MyShader. Перед использованием шейдеров необходимо проверить поддерживает ли драйвер видеокарты GLSL. Это можно сделать используя функцию bool IsGLSLSupported() класса MyShader:

bool MyShader::IsGLSLSupported()
{
	// читаем все расширения, которые поддерживает видеокарта
	char *szGLExtensions = (char*)glGetString(GL_EXTENSIONS);

	// проверяем поддержку видеокартой расширения GL_ARB_shader_objects
	// и если не поддерживает, то сообщаем об этом
	if(!strstr(szGLExtensions, "GL_ARB_shader_objects"))
	{
		MessageBox(0, "Расширение GL_ARB_shader_objects не поддерживается!", "Error", MB_OK);
		return false;
	}

	// удостоверимся в поддержке драйверов видеокарты GLSL версии 1.0
	if(!strstr(szGLExtensions, "GL_ARB_shading_language_100"))
	{
		MessageBox(0, " Расширение  GL_ARB_shading_language_100 не поддерживается!", "Error", MB_OK);
		return false;
    }
	// удостоверимся что драйвер видеокарты поддерживает функции-расширения для работы с 
	// GLSL-шейдерами
    if( !glCreateProgramObjectARB || !glDeleteObjectARB || !glUseProgramObjectARB 
			||!glCreateShaderObjectARB || !glCreateShaderObjectARB || !glCompileShaderARB 
			|| !glAttachObjectARB ||!glLinkProgramARB||!glGetUniformLocationARB 
			|| !glUniform2fARB || !glUniform3fARB||!glUniform4fARB 
			||!glUniform1iARB || !glUniform1fARB || !glDetachObjectARB)
	{
        MessageBox(NULL,"Одна или более функций для работы с шейдерами не поддерживается",
        "ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;
	}

	return true;
}

Также в функции bool IsGLSLSupported() класса MyShader осуществляется проверка инициализации функций-расширений, предназначенных для работы с шейдерами. В случае успешной инициализации переменная будет содержать адрес соответствующей функции, отличный от нуля. Инициализация этих функций реализована в другом файле и должна быть выполнена до проверки успешности их инициализации. Более подробно механизм работы с расширениями будет описан в другой статье. Эту проверку можно не делать, поскольку если драйвер видеокарты поддерживает GLSL, то функции-расширения для работы с шейдерами будут поддерживаться практически со 100% вероятностью. Здесь я все-таки приведу код инициализации расширений для работы с GLSL-шейдерами. Функция InitGLSL() не входит в класс MyShader.

void InitGLSL()
{
	glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) wglGetProcAddress ("glCreateShaderObjectARB");
	glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC ) wglGetProcAddress ("glShaderSourceARB");
	glCompileShaderARB = PFNGLCOMPILESHADERARBPROC) wglGetProcAddress ("glCompileShaderARB");
	glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) wglGetProcAddress ("glCreateProgramObjectARB");
	glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) wglGetProcAddress ("glAttachObjectARB");
	glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)wglGetProcAddress("glLinkProgramARB");
	glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) wglGetProcAddress ("glUseProgramObjectARB");
	glUniform1iARB = (PFNGLUNIFORM1IARBPROC)wglGetProcAddress("glUniform1iARB");
	glUniform1fARB = (PFNGLUNIFORM1FARBPROC)wglGetProcAddress("glUniform1fARB");
	glUniform2fARB = (PFNGLUNIFORM2FARBPROC)wglGetProcAddress("glUniform2fARB");
	glUniform3fARB = (PFNGLUNIFORM3FARBPROC)wglGetProcAddress("glUniform3fARB");
	glUniform4fARB = (PFNGLUNIFORM4FARBPROC)wglGetProcAddress("glUniform4fARB");
	glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) wglGetProcAddress ("glGetUniformLocationARB");
	glDetachObjectARB = (PFNGLDETACHOBJECTARBPROC) wglGetProcAddress ("glDetachObjectARB");
	glDeleteObjectARB  = (PFNGLDELETEOBJECTARBPROC) wglGetProcAddress ("glDeleteObjectARB");
}

Следующим шагом будет загрузка исходного текста шейдера в буфер памяти. Предполагается, что исходный текст шейдера хранится в текстовом файле. Для чтения данных из текстового файла служит функция char * LoadFromFile(char * shader_name) из класса MyShader. В качестве входного параметра функция принимает имя текстового файла и после выполнения возвращает указатель на область памяти, в которой хранится содержимое прочитанного файла - текст шейдера.


char * MyShader::LoadFromFile(char * shader_name)
{
	char * data;
	file = fopen(shader_name,"rb");
	if(!file)
	{
        MessageBox(NULL,"Невозможно открыть файл с текстом шейдера ",
        "ERROR",MB_OK|MB_ICONEXCLAMATION);
		return NULL;
	}
	
	fseek(file,0,SEEK_END);
	int size = ftell(file);
	data = new char[size + 1];
	data[size] = '\0';
	fseek(file,0,SEEK_SET);
	fread(data,1,size,file);
	fclose(file);
	return data;
}

Шейдерную программу OpenGL хранит в виде объекта, который содержит не только саму программу но и внутренние переменные, которые описывают состояние программы. Поэтому необходимо создать пустой объект-шейдер при помощи функции GLhandleARB glCreateShaderObjectARB(GLenum shaderType)
Параметры функции:
shaderType
Специфический тип шейдера, который нам необходимо создать. Может принимать только два значения GL_VERTEX_SHADER_ARB или GL_FRAGMENT_SHADER_ARB для вершинного или фрагментного шейдеров соответственно.

После выполнения функция возвращает ненулевой заголовок созданного объекта-шейдера, которое в дальнейшем будет использоваться для последующей идентификации этого объекта во многих функциях.
Практический пример использования:

GLhandleARB vsh_object = glCreateShaderObjectARB (GL_VERTEX_SHADER_ARB);

Этот код создал пустой вершинный объект-шейдер.

Далее необходимо поместить исходный код в пустой объект-шейдер используя функцию void glShaderSourceARB(GLhandleARB shader, GLsizei nstrings, const GLcharARB **strings, const GLint *lengths).
Параметры функции:
shaderType
Указывает заголовок объекта-шейдера, в который необходимо поместить исходный код. Для инициализации этого параметра используется функция glCreateShaderObjectARB(), см. выше.
nstrings
Указывает число символьных строк или символьных массивов.
strings
Является указателем на массив, который в свою очередь содержит указатели на символьные строки или массивы символов, содержащие исходный код, который будет загружен в объект-шейдер.
lengths
Указывает на массив, содержащий размер символьной строки или массива символов с исходным кодом шейдера.

Практический пример использования:

glShaderSourceARB(vsh_object, 1, &vsh_data, NULL);

Этот код поместил в пустой вершинный объект-шейдер код шейдера, прочитанный из файла.

Следующим шагом является компиляция каждого объекта-шейдера при помощи функции void glCompileShaderARB(GLhandleARB shader).
Параметры функции:
shader
Указывает заголовок объекта-шейдера, компиляцию которого необходимо выполнить. Для инициализации этого параметра используется функция glCreateShaderObjectARB(), см. выше.

Практический пример использования:

glCompileShaderARB(vsh_object);

Этот код компилирует вершинный объект-шейдер.

После компиляции всех объектов-шейдеров, необходимо создать пустую объект- программу, которая будет содержать в себе объекты-шейдеры. Для создания пустого объекта-программы служит функция GLhandleARB glCreateProgramObjectARB(void). Функция не имеет входных параметров и возвращает уникальный заголовок программы.
Практический пример использования:

GLhandleARB sh_program_object = glCreateProgramObjectARB();

Этот код создал пустую шейдерную объект-программу.

После создания пустой объекта-программы, необходимо присоединить к ней откомпилированные объекты-шейдеры используя функцию void glAttachObjectARB(GLhandleARB program, GLhandleARB shader).
Параметры функции:
program
Указывает заголовок объекта-программы, к которому необходимо присоединить объекты-шейдеры. Для инициализации этого параметра используется функция glCreateProgramObjectARB(), см. выше.
shader
Указывает заголовок объекта-шейдера, который необходимо присоединить к программе.

Практический пример использования:

glAttachObjectARB(sh_program_object, vsh_object);
	glAttachObjectARB(sh_program_object, fsh_object);

Этот код присоединяет к пустой шейдерной объекту-программе вершинный и фрагментный объекты-шейдеры.

Осталось теперь выполнить компоновку объекта-программы при помощи функции void glLinkProgramARB(GLhandleARB program).
Параметры функции:
program
Указывает заголовок объекта-программы, компоновку которого необходимо выполнить. Для инициализации этого параметра используется функция glCreateProgramObjectARB(), см. выше.

Практический пример использования:

glLinkProgramARB(sh_program_object);

Этот код выполняет компоновку шейдерного объекта-программы с заголовком sh_program_object

Вот и всё!!! Теперь перед рисованием наших объектов необходимо установить исполняемую программу в качестве текущей для контекста рендеринга OpenGL при помощи функции void glUseProgramObjectARB(GLhandleARB program).
Параметры функции:
program
Указывает заголовок объекта-программы, которую необходимо установить текущей для контекста рендеринга OpenGL. Для инициализации этого параметра используется функция glCreateProgramObjectARB(), см. выше.

Практический пример использования:

glUseProgramObjectARB(sh_program_object);

Этот код устанавливает шейдерный объект-программу с заголовком sh_program_object текущей дя контекста рендеринга OpenGL. После окончания рисования необходимо запретить выполнение шейдерного объекта-программы вызвав функцию glUseProgramObjectARB(0).

Примечание 1: Перед завершеним программы необходимо отсоединить все объекты-шейдеры от шейдерного объекта-программы при помощи функции void glDetachObjectARB (GLhandleARB program, GLhandleARB shader).
Параметры функции:
program
Указывает заголовок объекта-программы, от которой необходимо отсоединить объекты-шейдеры. Для инициализации этого параметра используется функция glCreateProgramObjectARB(), см. выше.
shader
Указывает заголовок объекта-шейдера, который необходимо отсоединить от программы.

После этого необходимо удалить отсоединённые от объекта-программы объекты-шейдеры и после этого удалить собственно сам шейдерный объект-программу при помощи функции void glDeleteObjectARB(GLhandleARB object).
Параметры функции:
object
Указывает заголовок объекта-шейдера или объекта-программы, который необходимо удалить. Для инициализации этого параметра используются функции glCreateShaderObjectARB(),glCreateProgramObjectARB(),см. выше.

Функция glDeleteObjectARB() освобожадает область памяти, выделенную под объект и делает недействительным заголовок объекта. Практический пример использования:

	if(vsh_object)
	{
		glDetachObjectARB(sh_program_object, vsh_object);
		glDeleteObjectARB(vsh_object);
		vsh_object = NULL;
	}
Этот код демонстрирует как отсоединяется вершинный объект-шейдер от объекта-программы и как производится удаление вершинного объекта-шейдера.

Примечание 2: существует функция, которая сообщает о том, как выполнялись операции по компиляции, присоединению, компоновке, удалению шейдеров. Иначе говоря, эта функция даёт информацию о статусе объекта (шейдера, программы) после выполнения некоторых действий над этим объектом. Это функция void glGetObjectParameterf(i)vARB (GLhandleARB object,GLenum pname, GLfloat (GLint) *params).
Параметры функции:
object
Указывает заголовок объекта, о статусе которого необходимо получить информацию. Для инициализации этого параметра используется функция glCreateProgramObjectARB(), см. выше.
pname
Указывает конкретный параметр, информацию о котором мы хотим получить. Вот перечень символических названий параметров, о которых можно получить информацию: GL_OBJECT_TYPE_ARB, GL_OBJECT_SUBTYPE_ARB, GL_OBJECT_DELETE_STATUS_ARB, GL_OBJECT_LINK_STATUS_ARB, GL_OBJECT_VALIDATE_STATUS_ARB, GL_OBJECT_COMPILE_STATUS_ARB, GL_OBJECT_INFO_LOG_LENGTH_ARB, GL_OBJECT_ATTACHED_OBJECTS_ARB, GL_OBJECT_ACTIVE_ATTRIBUTES_ARB, GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB, GL_OBJECT_ACTIVE_UNIFORMS_ARB, GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB.
params
Возвращает информацию о состоянии запрашиваемого параметра. Принимает одно из двух значений GL_TRUE или GL_FALSE.

Практический пример использования:

GLint status = GL_FALSE;
glGetObjectParameterivARB(fsh_object, GL_OBJECT_COMPILE_STATUS_ARB, &status);
	if (status== GL_FALSE)
	{
		MessageBox(NULL,"Fragment Shader not compiled",
        "ERROR",MB_OK|MB_ICONEXCLAMATION);
	}
Этот код проверяет как произошла компиляция фрагментного объекта-шейдера. Если переменная status равна GL_TRUE, то компиляция прошла успешно. Исходный код класса для работы с GLSL-шейдерами можно загрузить здесь. В качестве примера будет использоваться шейдер, выполняющий преобразование цветной RGB-картинки в картинку с оттенками серого. Исходный код и исполняемый файл примера к статье можно загрузить здесь. В процессе написания последующих статей о работе с шейдерами, класс MyShader будет пополняться новыми функциями. Удачи Вам.

Hosted by uCoz